#include "Limelight-internal.h"
#include "PortManager.h"

#define TEST_PORT_TIMEOUT_SEC 3

#define VALID_PORT_FLAG_MASK (ML_PORT_FLAG_TCP_47984 | \
                              ML_PORT_FLAG_TCP_47989 | \
                              ML_PORT_FLAG_TCP_48010 | \
                              ML_PORT_FLAG_UDP_47998 | \
                              ML_PORT_FLAG_UDP_47999 | \
                              ML_PORT_FLAG_UDP_48000 | \
                              ML_PORT_FLAG_UDP_48010)

#define PORT_FLAGS_MAX_COUNT 32

#define MTU_TEST_SIZE 1040

#define MAXPATH 1024

unsigned int LiGetPortFlagsFromStage(int stage) {
    switch (stage) {
        case STAGE_RTSP_HANDSHAKE:
            // GFE 3.22需要在48000上成功ping才能完成RTSP握手
            return ML_PORT_FLAG_TCP_48010 | ML_PORT_FLAG_UDP_48010 | ML_PORT_FLAG_UDP_48000;

        case STAGE_CONTROL_STREAM_START:
            return ML_PORT_FLAG_UDP_47999;

        default:
            return 0;
    }
}

unsigned int LiGetPortFlagsFromTerminationErrorCode(int errorCode) {
    switch (errorCode) {
        case ML_ERROR_NO_VIDEO_TRAFFIC:
            return ML_PORT_FLAG_UDP_47998 | ML_PORT_FLAG_UDP_48000;

        default:
            return 0;
    }
}

int LiGetProtocolFromPortFlagIndex(int portFlagIndex) {
    // 较低的字节为TCP保留
    return (portFlagIndex >= 8) ? IPPROTO_UDP : IPPROTO_TCP;
}

//TODO: 替换端口
unsigned short LiGetPortFromPortFlagIndex(int portFlagIndex) {
    switch (portFlagIndex) {
         // TCP ports
         case ML_PORT_INDEX_TCP_47984:
            return getCustomPortFromPortFlagIndex(47984);
             break;
             
         case ML_PORT_INDEX_TCP_47989:
            return getCustomPortFromPortFlagIndex(47989);
             break;
             
         case ML_PORT_INDEX_TCP_48010:
            return getCustomPortFromPortFlagIndex(48010);
             break;
             
         // UDP ports
         case ML_PORT_INDEX_UDP_47998:
            return getCustomPortFromPortFlagIndex(47998);
             break;
             
         case ML_PORT_INDEX_UDP_47999:
            return getCustomPortFromPortFlagIndex(47999);
             break;
             
         case ML_PORT_INDEX_UDP_48000:
            return getCustomPortFromPortFlagIndex(48000);
             break;
             
         case ML_PORT_INDEX_UDP_48010:
            return  getCustomPortFromPortFlagIndex(48010);
             break;
             
         default:
            return  getCustomPortFromPortFlagIndex(0);
             break;
     }
}

void LiStringifyPortFlags(unsigned int portFlags, const char* separator, char* outputBuffer, int outputBufferLength) {
    // 将输出缓冲区初始化为空字符串
    outputBuffer[0] = 0;

    //如果没有指定分隔符，则使用空字符串
    if (separator == NULL) {
        separator = "";
    }

    int offset = 0;
    for (int i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
        if (portFlags & (1U << i)) {
            const char* protoStr = LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? "UDP" : "TCP";
            offset += snprintf(&outputBuffer[offset], outputBufferLength - offset, "%s%s %u",
                               offset != 0 ? separator : "",
                               protoStr,
                               LiGetPortFromPortFlagIndex(i));
            if (outputBufferLength - offset <= 0) {
                //如果缓冲区太小，snprintf()将返回所需的长度，
                //因此，这个计算可能是负的。
                break;
            }
        }
    }
}

unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags) {
    unsigned int failingPortFlags;
    struct sockaddr_storage address;
    SOCKADDR_LEN address_length;
    int i;
    int err;
    SOCKET sockets[PORT_FLAGS_MAX_COUNT];

    // 从端口标志中屏蔽掉无效端口
    testPortFlags &= VALID_PORT_FLAG_MASK;
    failingPortFlags = testPortFlags;

    // 如果没有指定端口，只返回0
    if (testPortFlags == 0) {
        return 0;
    }

    // Initialize sockets array to -1
    memset(sockets, 0xFF, sizeof(sockets));

    err = initializePlatformSockets();
    if (err != 0) {
        Limelog("Failed to initialize sockets: %d\n", err);
        return ML_TEST_RESULT_INCONCLUSIVE;
    }

    err = resolveHostName(testServer, AF_UNSPEC, TCP_PORT_FLAG_ALWAYS_TEST | referencePort, &address, &address_length);
    if (err != 0) {
        failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
        goto Exit;
    }

    for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
        if (testPortFlags & (1U << i)) {
            sockets[i] = createSocket(address.ss_family,
                                      LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM,
                                      LiGetProtocolFromPortFlagIndex(i),
                                      true);
            if (sockets[i] == INVALID_SOCKET) {
                err = LastSocketFail();
                Limelog("Failed to create socket: %d\n", err);
                failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
                goto Exit;
            }

            SET_PORT((LC_SOCKADDR*)&address, LiGetPortFromPortFlagIndex(i));
            if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_TCP) {
                // Initiate an asynchronous connection
                err = connect(sockets[i], (struct sockaddr*)&address, address_length);
                if (err < 0) {
                    err = (int)LastSocketError();
                    if (err != EWOULDBLOCK && err != EAGAIN && err != EINPROGRESS) {
                        Limelog("Failed to start async connect to TCP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);

                        // 屏蔽掉这一点，这样我们就不会试图将其包含在下面的pollSockets()中
                        testPortFlags &= ~(1U << i);
                    }
                }
            }
            else {
                const char buf[MTU_TEST_SIZE] = "moonlight-ctest";
                int j;

                // Send a few packets since UDP is unreliable
                for (j = 0; j < 3; j++) {
                    err = sendto(sockets[i], buf, sizeof(buf), 0, (struct sockaddr*)&address, address_length);
                    if (err < 0) {
                        err = (int)LastSocketError();
                        Limelog("Failed to send test packet to UDP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);

                        // 屏蔽掉这一点，这样我们就不会试图将其包含在下面的pollSockets()中
                        testPortFlags &= ~(1U << i);

                        break;
                    }

                    PltSleepMs(50);
                }
            }
        }
    }

    //继续调用pollSockets()，直到我们没有更多的套接字等待，
    //或者我们的pollSockets()调用超时。
    while (testPortFlags != 0) {
        int nfds;
        struct pollfd pfds[PORT_FLAGS_MAX_COUNT];

        nfds = 0;

        // Fill out our FD sets
        for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
            if (testPortFlags & (1U << i)) {
                pfds[nfds].fd = sockets[i];

                if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP) {
                    // 注意UDP套接字的可读性
                    pfds[nfds].events = POLLIN;
                }
                else {
                    // 注意TCP套接字上的可写性
                    pfds[nfds].events = POLLOUT;
                }

                nfds++;
            }
        }

        //等待执行完成或超时。
        //注意:每次我们在端口上获得有效响应时，超时都会重置。
        //但这可能很好。
        err = pollSockets(pfds, nfds, TEST_PORT_TIMEOUT_SEC * 1000);
        if (err < 0) {
            // pollSockets() failed
            err = LastSocketError();
            Limelog("pollSockets() failed: %d\n", err);
            failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
            goto Exit;
        }
        else if (err == 0) {
            // pollSockets() timed out
            Limelog("Connection timed out after %d seconds\n", TEST_PORT_TIMEOUT_SEC);
            break;
        }

        // 我们知道有人发出了信号。现在我们只需要找出是什么。
        for (i = 0; i < nfds; i++) {
            if (pfds[i].revents != 0) {
                int portIndex;

                // 这个套接字被发送了信号。找出它是哪个端口。
                for (portIndex = 0; portIndex < PORT_FLAGS_MAX_COUNT; portIndex++) {
                    if (sockets[portIndex] == pfds[i].fd) {
                        LC_ASSERT(testPortFlags & (1U << portIndex));
                        break;
                    }
                }

                LC_ASSERT(portIndex != PORT_FLAGS_MAX_COUNT);

                if (LiGetProtocolFromPortFlagIndex(portIndex) == IPPROTO_UDP) {
                    char buf[MTU_TEST_SIZE];

                    //发送UDP套接字信号。这可能是因为我们得到
                    //来自测试服务器的数据包，也可能是因为我们
                    //收到一个ICMP错误，该错误将从
                    // recvfrom()。
                    testPortFlags &= ~(1U << portIndex);

                    // Check if the socket can be successfully read now
                    err = recvfrom(sockets[portIndex], buf, sizeof(buf), 0, NULL, NULL);
                    if (err >= 0) {
                        // The UDP test was a success.
                        failingPortFlags &= ~(1U << portIndex);

                        Limelog("UDP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
                    }
                    else {
                        err = LastSocketError();
                        Limelog("UDP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err);
                    }
                }
                else {
                    // A TCP socket was signalled
                    SOCKADDR_LEN len = sizeof(err);
                    getsockopt(sockets[portIndex], SOL_SOCKET, SO_ERROR, (char*)&err, &len);
                    if (err != 0 || (pfds[i].revents & POLLERR)) {
                        // Get the error code
                        err = (err != 0) ? err : LastSocketFail();
                    }

                    // The TCP test has completed for this port
                    testPortFlags &= ~(1U << portIndex);
                    if (err == 0) {
                        // The TCP test was a success
                        failingPortFlags &= ~(1U << portIndex);

                        Limelog("TCP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
                    }
                    else {
                        Limelog("TCP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err);
                    }
                }
            }
        }

        //下一次迭代，我们将从FD集合和中删除匹配的套接字
        //再次调用select()等待剩余的套接字。
    }

Exit:
    for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
        if (sockets[i] != INVALID_SOCKET) {
            closeSocket(sockets[i]);
        }
    }

    cleanupPlatformSockets();
    return failingPortFlags;
}
